////////////////////////////////////////////////////////////////////////////////
//
// 宣言
//
////////////////////////////////////////////////////////////////////////////////
/**
 * グローバル変数
 */
// ポート番号
var port = 80;

// MySQL データベース名、ユーザ名、パスワード
var DB_NAME = "bubble";
var DB_USER = "app_user";
var DB_PASSWD = "app";

// トピックIDのバッファ
var g_topic_id = [];

// 接続中ユーザの管理リスト
var g_connected_user = [];

/*
 * モジュールの読み込み
 */
var express = require("express")
  , ejs = require("ejs")
  , io = require("socket.io")
  , sys = require("sys")
  , Client = require("mysql").Client;

////////////////////////////////////////////////////////////////////////////////
//
// MySQL
//
////////////////////////////////////////////////////////////////////////////////
/*
 * MySQLデータベースに接続しcallbackを呼び出す
 */
function mysql(callback) {
	var client = new Client();
	client.database = DB_NAME;
	client.user = DB_USER;
	client.password = DB_PASSWD;

	client.connect(function(err) {
		if (err) {
			throw err;
		}
		callback(client);
	});
}

////////////////////////////////////////////////////////////////////////////////
//
// メッセージ処理関数
//
////////////////////////////////////////////////////////////////////////////////
/**
 * トピック情報のバッファをメモリ上に作成する
 * @param {string} topic_id トピックID
 */
function bufferTopic () 
{
	// mysql 実行（メッセージの取得）
	mysql(function(mysql_client) {
		mysql_client.query(
			"SELECT " + 
			"	IFNULL(MAX(topic_id) + 1, 1) max_topic_id " +
			"FROM topic",
			function(err, results, fields) {
				mysql_client.end();
				if (err) {
					throw err;
				}
				else if (results.length == 0) {
					throw new Error("can't get max_value of topic_id");
				}
				else {
					var max_topic_id = results[0].max_topic_id;
					for (var i = max_topic_id; i < max_topic_id + 10; i ++) {
						g_topic_id.push(i);
					}
				}
			}
		);
	});
}

/**
 * トピック情報を取得する
 * @param {object} client クライアントオブジェクト
 */
function getTopicList (client) 
{
	var topic_list = [];
	
	// mysql 実行（メッセージの取得）
	mysql(function(mysql_client) {
		mysql_client.query(
			"SELECT " + 
			"	topic_id, subject, description " +
			"FROM topic ",
			function(err, results, fields) {
				mysql_client.end();
				if (err) {
					throw err;
				}
				else if (results.length == 0) {
					// do nothing
				}
				else {
					for(var i = 0; i < results.length; i++) 
					{
						topic_list.push({topic: [results[i].topic_id, results[i].subject, results[i].description]});
					}
					//クライアントに配信
					client.send({topic_list: topic_list});
				}
			}
		);
	});
}

/**
 * トピック情報を取得する
 * @param {object} client クライアントオブジェクト
 * @param {string} topic_id トピックID
 */
function getTopic (client, topic_id) 
{
	var topic;
	
	// mysql 実行（メッセージの取得）
	mysql(function(mysql_client) {
		mysql_client.query(
			"SELECT " + 
			"	subject, description " +
			"FROM topic " +
			"WHERE " + 
			"	topic_id = ?",
			[topic_id], 
			function(err, results, fields) {
				mysql_client.end();
				if (err) {
					throw err;
				}
				else if (results.length == 0) {
					topic = {topic: [topic_id, "トピックの名称が見つかりません", ""]};
				}
				else {
					topic = {topic: [topic_id, results[0].subject, results[0].description]};
					//クライアントに配信
					client.send(topic);
				}
			}
		);
	});
}

/**
 * トピック情報を登録する
 * @param {string} topic_id トピックID
 * @param {string} subject 名称
 * @param {string} description 説明
 */
function registTopic (topic_id, subject, description) 
{
	// 引数のチェック
	if (subject.length == 0) {
		subject = "無題";
	}

	// mysql 実行（メッセージの登録）
	mysql(function(mysql_client) {
		mysql_client.query(
		   "INSERT INTO topic " +
		   "	(topic_id, subject, description) " + 
		   "	VALUES (?, ?, ?)",
		   [topic_id, subject, description],
		   function(err, results) {
				mysql_client.end();
				
				if (err) {
					throw err;
				}
			}
		 );
	});
}

/**
 * ユーザーリストを取得する。
 * @param {object} client クライアントオブジェクト
 * @param {string} criteria_date データ取得基準日時
 */
function getUserList(client, criteria_date)
{
	var user_list = [];

	mysql(function(mysql_client)
	{
		mysql_client.query(
			"SELECT " +
			"       user_name, session_id " +
			"FROM users ",
//         + "WHERE update_date >= ?",
//         [criteria_date],
			function(err, results, fields)
			{
				mysql_client.end();

				if (err) {
					throw err;
				}

				for(var i = 0; i < results.length; i++)
				{
					user_list.push({
						name: results[i].user_name,
						session_id: results[i].session_id
					});
				}
				
				//クライアントに配信
				client.send({user_list: user_list});
			}
		);
	});
}

/**
 * ユーザー情報を登録する。
 * @param {Object} client クライアントオブジェクト
 * @param {Object} user_info ユーザー情報
 */
function registerUser(client, user_info)
{
	if (user_info.user_name.length == 0)
	{
		user_info.user_name = "名無し";
	}

	// 全ユーザーへ配信
	var new_user = {
		name: user_info.user_name,
		session_id: client.sessionId,
		topic_id: user_info.topic_id
	};
	client.send({new_user:  new_user});
	client.broadcast({new_user:  new_user});

	mysql(function(mysql_client)
	{
		mysql_client.query(
			"INSERT INTO users " +
			"		(user_name, session_id, register_date, update_date) " +
			"		VALUES (?, ?, now(), now())",
			[user_info.user_name, client.sessionId],
			function (err, result)
			{
				mysql_client.end();

				if (err)
				{
					throw err;
				}
			}
		);
	});
}

/**
 * テキスト情報をデータベースから取得する
 * @param {object} client クライアントオブジェクト
 * @param {string} topic_id トピックID
 */
function load (client, topic_id) 
{
	var idea_map = [];

	// mysql 実行（メッセージの取得）
	mysql(function(mysql_client) {
		mysql_client.query(
			"SELECT " + 
			"	idea_id, parent_idea_id, user_id, text, image, color, agree_count, CONVERT_TZ(registered, '+09:00', '+00:00') registered " +
			"FROM idea " +
			"WHERE " + 
			"	topic_id = ?",
			[topic_id],
			function(err, results, fields) {
				mysql_client.end();
				if (err) {
					throw err;
				}
				else if (results.length == 0) {
					//throw new Error("data is nothing");
				}
				else {
					for (var i = 0; i < results.length; i ++) {
						var msg = null;
						// イメージ情報を取得
						if (results[i].image) {
							msg = { image: [results[i].user_id, topic_id, results[i].idea_id, results[i].image, results[i].color, results[i].parent_idea_id] };
						}
						// テキスト情報を取得
						else {
							msg = { message: [results[i].user_id, topic_id, results[i].idea_id, results[i].text, results[i].color, results[i].parent_idea_id] };
						}
						idea_map.push(msg);
					}
					// クライアントに今までのメッセージ履歴（バッファ）を送信する
					client.send({ buffer: idea_map });
				}
			}
		);
	});
}

/**
 * テキスト情報を処理する
 * @param {object} client クライアントオブジェクト
 * @param {object} obj メッセージオブジェクト
 */
function message (client, obj) 
{
	var topic_id = obj.message[0];
	var message = obj.message[1];
	var color = obj.message[2];
	var parent_id = obj.message[3];
	
	// mysql 実行（メッセージの登録）
	mysql(function(mysql_client) {
		mysql_client.query(
		   "INSERT INTO idea " +
		   "	(topic_id, parent_idea_id, user_id, text, color) " + 
		   "	VALUES (?, ?, ?, ?, ?)",
		   [topic_id, parent_id, client.sessionId, message, color],
		   function(err, results) {
				if (err) {
					mysql_client.end();
					throw err;
				}
				else {
					mysql_client.end();
					
					var msg = 
						{ message: [
							client.sessionId, 
							topic_id, 
							results.insertId, 
							message, 
							color, 
							parent_id] };
					var msg_you = 
						{ message: [
							client.sessionId, 
							topic_id,
							results.insertId, 
							message, 
							color, 
							parent_id] };
							
					// 結果IDを連携オブジェクトにセットする
					msg.message[2] = msg_you.message[2] = results.insertId;
					//クライアントに配信
					client.send(msg_you);
					// 他のクライアントに配信
					client.broadcast(msg);
				}
			}
		 );
	});
}

/**
 * イメージ情報を処理する
 * @param {object} client クライアントオブジェクト
 * @param {object} obj メッセージオブジェクト
 */
function image (client, obj) {
	var topic_id = obj.image[0];
	var img = obj.image[1];
	var color = obj.image[2];
	var parent_id = obj.image[3];
	
	// mysql 実行（メッセージの登録）
	mysql(function(mysql_client) {
		mysql_client.query(
		   "INSERT INTO idea " +
		   "	(topic_id, parent_idea_id, user_id, image, color) " + 
		   "	VALUES (?, ?, ?, ?, ?)",
		   [topic_id, parent_id, client.sessionId, img, color],
		   function(err, results) {
				if (err) {
					mysql_client.end();
					throw err;
				}
				if (!err) {
					mysql_client.end();
					
					var msg = 
						{ image: [
							client.sessionId, 
							topic_id, 
							results.insertId, 
							img, 
							color, 
							parent_id] };
					var msg_you = 
						{ image: [
							client.sessionId, 
							topic_id, 
							results.insertId, 
							img, 
							color, 
							parent_id] };

					//クライアントに配信
					client.send(msg_you);
					// 他のクライアントに配信
					client.broadcast(msg);
				}
			}
		 );
	});
}

/**
 * 関連付け情報を処理する
 * @param {object} client クライアントオブジェクト
 * @param {object} obj メッセージオブジェクト
 */
function linkage (client, obj) {
	var obj_id = obj.linkage[1];
	var parent_id = obj.linkage[2];
	
	// mysql 実行（関連付けの更新）
	mysql(function(mysql_client) {
		mysql_client.query(
		   "UPDATE idea " +
		   "SET parent_idea_id = ? " + 
		   "WHERE " +
		   "	idea_id = ?",
		   [parent_id, obj_id],
		   function(err, results) {
				if (err) {
					mysql_client.end();
					throw err;
				}
				else {
					mysql_client.end();
					
					// 他のクライアントに配信
					client.broadcast(obj);
				}
			}
		 );
	});
}

////////////////////////////////////////////////////////////////////////////////
//
// データ準備
//
////////////////////////////////////////////////////////////////////////////////
/**
 * トピック情報のバッファを用意する
 */
 bufferTopic();

////////////////////////////////////////////////////////////////////////////////
//
// HTTP
//
////////////////////////////////////////////////////////////////////////////////
/*
 * サーバの準備
 */
// サーバを作成
var app = express.createServer();

// 静的ファイルのフォルダ
app.configure(function(){
	app.use(express.staticProvider(__dirname + "/public")),
	app.use(express.bodyDecoder());
});
// ejsテンプレートを利用する
app.set("view engine", "ejs");
// レイアウトを利用しない
app.set("view options", {layout: false});

/*
 * GETリクエストを受け取る
 */
app.get("/", function(req, res){
	res.redirect("/index");
});

/*
 * トピック新規作成のGETリクエストを受け取る
 */
app.get("/new", function(req, res){
	var topic_id = g_topic_id.shift();
	res.redirect("/index?topic_id=" + topic_id);

	// bufferを増やす
	g_topic_id.push(g_topic_id[g_topic_id.length - 1] + 1);

	// トピックの登録
	registTopic(topic_id, req.param("subject"), req.param("description"));
});

/*
 * トピック読み込みのGETリクエストを受け取る
 */
app.get("/index", function(req, res){
	var topic_id = 0;
	if (req.param("topic_id")) {
		topic_id = req.param("topic_id");
	}
	res.render("index", {
		locals: {
			port: port,
			topic_id: topic_id
		}});
});

/*
 * サーバを起動する
 */
app.listen(port);

////////////////////////////////////////////////////////////////////////////////
//
// WebSocket
//
////////////////////////////////////////////////////////////////////////////////
/*
 * WebSocketを起動する
 */
var io = io.listen(app);
  
/** 
 * クライアントの接続時
 */
io.on('connection', function(client){
	/**
	 * クライアントからの受信時
	 */
	client.on('message', function(obj){
		// テキスト情報受信の場合
		if ("message" in obj) {
			message(client, obj);
		}
		// イメージ情報受信の場合
		else if ("image" in obj) {
			image(client, obj);
		}
		// 関連付け情報受信の場合
		else if ("linkage" in obj) {
			linkage(client, obj);
		}
		// トピック情報のロードを要求している場合
		else if ("load" in obj) {
			var topic_id = obj.load[0];
		
			getTopic(client, topic_id);
			load(client, topic_id);
		}
		// トピック情報一覧のロードを要求している場合
		else if ("topiclist" in obj) {
			getTopicList(client);
		}
		// ユーザー情報一覧のロードを要求している場合
		else if ("userlist" in obj) {
			getUserList(client, null);
		}
		// ユーザー登録を要求している場合
		else if ("registeruser" in obj) {
			registerUser(client, obj.registeruser);

			// 接続ユーザ情報を保存する
			g_connected_user[String(client.sessionId)] = obj.registeruser.topic_id;
			// 接続ユーザ情報をリストにする
			var connected_user_list = [];
			for (var key in g_connected_user)
			{
				if (g_connected_user[key] == obj.registeruser.topic_id) {
					connected_user_list.push(key);
				}
			}
			// クライアントに接続ユーザリストを配信する
			client.send({ connected_user_list: connected_user_list });
		}
	});

	/*
	 * クライアントの切断時
	 */
	client.on('disconnect', function(){
		var sessionId = String(client.sessionId);
	
		// 他のクライアントに切断通知メッセージを送信する
		var delete_user = {
			session_id: sessionId,
			topic_id: g_connected_user[sessionId]
		};
		client.broadcast({delete_user: delete_user});
		// 接続ユーザ情報を削除する
		delete g_connected_user[sessionId]; 
	});
});
